package com.dux.commons.scripts.java.impl.javacompiler;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;

import org.apache.log4j.Logger;

import com.dux.commons.scripts.java.CompilationResult;
import com.dux.commons.scripts.java.ScriptClassLoader;
import com.dux.commons.scripts.java.ScriptCompiler;



/**
 * java编译器包装类
 * @author 杜祥
 * @create 2013-6-19
 */
public class ScriptCompilerImpl implements ScriptCompiler
{
	private static final Logger log = Logger.getLogger(ScriptCompilerImpl.class);
	
	/**
	 * java编译器，用于编译类的实例
	 */
	protected final JavaCompiler javaCompiler;
	
	/**
	 * jar文件列表
	 */
	protected Iterable<File> libraries;
	
	/**
	 * 父类加载器
	 */
	protected ScriptClassLoader parentClassLoader;
	
	
	public ScriptCompilerImpl()
	{
		this.javaCompiler = ToolProvider.getSystemJavaCompiler();
		
		if(javaCompiler == null)
		{
			if(ToolProvider.getSystemJavaCompiler() == null)
			{
				throw new RuntimeException(new InstantiationException("java编译器不可用！"));
			}
		}
	}

	@Override
	public void setParentClassLoader(ScriptClassLoader classLoader) 
	{
		this.parentClassLoader = classLoader;
	}

	@Override
	public void setLibraires(Iterable<File> files)
	{
		this.libraries = files;
	}

	/**
	 * 编译指定的类名。
	 * @param className
	 *            类型
	 * @param sourceCode
	 *            源代码
	 * @return 编译结果类
	 * @throws RuntimeException
	 *             编译失败抛出此异常。
	 */
	@Override
	public CompilationResult compile(String className, String sourceCode) 
	{
		return compile(new String[] { className }, new String[] { sourceCode });
	}

	
	/**
	 * 编译类列表，类名与源码数量必须相等。
	 * @param className 
	 * 						类名
	 * @param sourceCode 
	 * 						源码
	 * @return			
	 * 						返回编译结果
	 * @throws RuntimeException
	 * 						编译失败抛出此异常。
	 * @throws IllegalArgumentException
	 * 						类名的数量和源代码数量不对应，抛出此异常。
	 */
	@Override
	public CompilationResult compile(String[] className, String[] sourceCode)throws IllegalArgumentException
	{
		if(className.length != sourceCode.length)
		{
			throw new IllegalArgumentException("类名的数量与源码的数量不相等！");
		}
		
		List<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>();
		
		for(int i = 0; i < className.length; i++)
		{
			JavaSourceFromString souceString = new JavaSourceFromString(className[i], sourceCode[i]);
			compilationUnits.add(souceString);
		}
		
		return doCompilation(compilationUnits);
	}

	/**
	 * 编译的文件，文件必须是java源码。
	 * @param compilationUnits 文件的路径
	 * @return  返回编译结果
	 * @throws RuntimeException
	 * 					编译失败抛出此异常。
	 */
	@Override
	public CompilationResult compile(Iterable<File> compilationUnits)
	{
		List<JavaFileObject> list = new ArrayList<JavaFileObject>();
		
		for(File f : compilationUnits)
		{
			list.add(new JavaSourceFromFile(f, JavaFileObject.Kind.SOURCE));
		}
		return doCompilation(list);
	}
	
	
	/**
	 * 编译指定的类库，编译器以UTF-8编码格式编译。
	 * @param compilationUnits
	 * @return
	 * 					返回编译结果
	 * @throws RuntimeException
	 * 					当指定的源代码无法编译时抛出此异常
	 */
	@SuppressWarnings("rawtypes")
	protected CompilationResult doCompilation(Iterable<JavaFileObject> compilationUnits)
	{
		List<String> options = Arrays.asList("-encoding", "UTF-8", "-g");
		
		DiagnosticListener<JavaFileObject> listener = new ErrorListener();
		
		ClassFileManager manager = new ClassFileManager(javaCompiler, listener);
		
		manager.setParentClassLoader(parentClassLoader);
		
		if(libraries != null)
		{
			try
			{
				manager.addLibraries(libraries);
			} 
			catch (IOException e) 
			{
				log.error("无法设置编译器文件库", e);
			}
		}
		
		JavaCompiler.CompilationTask task = javaCompiler.getTask(null, manager, listener, options, null, compilationUnits);
		
		if(!task.call())
		{
			throw new RuntimeException("编译类错误！");
		}
		
		ScriptClassLoader cl = manager.getClassLoader(null);
		
		Class[] compilerClasses = classNamesToClasses(manager.getCompiledClasses().keySet(), cl);
		
		return new CompilationResult(compilerClasses, cl);
	}
	
	
	/**
	 * 解析类名解析类列表
	 * @param classNames
	 * 						类名
	 * @param cl
	 * 						类加载器
	 * @return	返回解析的class
	 */
	@SuppressWarnings("rawtypes")
	protected Class[] classNamesToClasses(Collection<String> classNames, ScriptClassLoader cl)
	{
		Class<?>[] classes = new Class<?>[classNames.size()];
		
		int i = 0;
		
		for(String className : classNames)
		{
			try 
			{
				Class<?> clazz = cl.loadClass(className);
				classes[i] = clazz;
			}
			catch (ClassNotFoundException e)
			{
				throw new RuntimeException(e);
			}
			i++;
		}
		return classes;
	}

	@Override
	public String[] getSupportedFileTypes() 
	{
		return new String[]{"java"};
	}

}
